#pragma warning(push)
#pragma warning(disable:4996)

#include "login_server_imp.h"
#include "hex_dumper.h"

namespace login_server_detail
{
	void Imp::start(UPeer::Ptr peer)
	{
		peer_ = peer;
		AutoSeededRandomPool rng;
		auto package = new ChallengePackage();
		package->nonce_.reset(new unsigned char[SHA::DIGESTSIZE], SHA::DIGESTSIZE);
		challengeNonse_.reset(new unsigned char[SHA::DIGESTSIZE], SHA::DIGESTSIZE);
		rng.GenerateBlock(package->nonce_.get(), SHA::DIGESTSIZE);
		peer->send(package);
		copy(package->nonce_.get(), package->nonce_.get() + package->nonce_.size(), 
			challengeNonse_.get());

		currentState_ = State::Ptr(new ChallengePhase());
	}


	void Imp::onSetAuthInfo(const std::string& userName, const std::string& password)
	{
		userName_ = userName;
		password_ = password;

		SHA sha;
		sha.Update((const byte*) userName.data(), userName.length());
		sha.Update((const byte*) password.data(), password.length());
		scodigestOfUserPassword_.reset(new byte[SHA::DIGESTSIZE], SHA::DIGESTSIZE);
		sha.Final(scodigestOfUserPassword_.get());

		HMAC<SHA> hmac(scodigestOfUserPassword_.get(), scodigestOfUserPassword_.size());
		
		{	
			cout << "challenge phase, digest of user pass: " << 
				hex_dump::dump(scodigestOfUserPassword_.get(), SHA::DIGESTSIZE) << endl;
		}

		scoped_array<byte> signatureOfNonse(new byte[SHA::DIGESTSIZE], SHA::DIGESTSIZE);
		hmac.CalculateDigest(signatureOfNonse.get(), challengeNonse_.get(), SHA::DIGESTSIZE);
		{	
			cout << "challenge phase, hmac of server nones: " << 
				hex_dump::dump(signatureOfNonse.get(), SHA::DIGESTSIZE) << endl;
		}

		auto ret = memcmp(signatureOfNonse.get(), challengeSignature_.get(), SHA::DIGESTSIZE);
		if(ret != 0)
		{
			peer_->close();
			peer_.reset();
			return;
		}

		AutoSeededRandomPool rng;
		scoped_array<byte> secondNonse(new byte[SHA::DIGESTSIZE], SHA::DIGESTSIZE);
		rng.GenerateBlock(secondNonse.get(), secondNonse.size());

		hmac.Restart();
		hmac.Update((const byte*) userName.data(), userName.length());
		hmac.Update((const byte*) password.data(), password.length());
		hmac.Update((const byte*) secondNonse.get(), SHA::DIGESTSIZE);

		outKey_.reset(new byte[SHA::DIGESTSIZE], SHA::DIGESTSIZE);
		hmac.Final(outKey_.get());
		{
			cout << "challenge phase, out key: " << 
				hex_dump::dump(outKey_.get(), SHA::DIGESTSIZE) << endl;
		}

		auto keyExchange = new KeyExchangePackage();
		keyExchange->nonce_.swap(secondNonse);
		peer_->send(IPackage::Ptr(keyExchange));

		currentState_ = boost::shared_ptr<State>(new KeyExchangePhase());
	}

	void Imp::handle(IPackage* package, IIoHandler::Ptr ioHandler)
	{
		if(!currentState_->handle(package, this))
		{
			peer_.reset();
			ioHandler->close();
		}
	}

	void Imp::onKeyExchanged(const scoped_array<byte>& finalNonce)
	{
		HMAC<SHA> hmac(scodigestOfUserPassword_.get(), scodigestOfUserPassword_.size());
		hmac.Update((const byte*) userName_.data(), userName_.length());
		hmac.Update((const byte*) password_.data(), password_.length());
		hmac.Update((const byte*) finalNonce.get(), finalNonce.size());

		scoped_array<byte> inKey(new byte[SHA::DIGESTSIZE], SHA::DIGESTSIZE);
		hmac.Final(inKey.get());

		{
			cout << "key exchange phase, in key: " << 
				hex_dump::dump(inKey.get(), SHA::DIGESTSIZE) << endl;
		}
		auto outCodec = new Arc4Codec(outKey_);
		auto inCodec = new Arc4Codec(inKey);

		peer_->setAttachment(IIoAttachment::Ptr(new DuplexCodec(IPackageCodec::Ptr(outCodec), 
			IPackageCodec::Ptr(inCodec))));
		peer_.reset();
	}


	bool KeyExchangePhase::handle(IPackage* package, Imp* loginClient)
	{
		auto exchangePackage = static_cast<KeyExchangeResponse*>(package);
		if(!exchangePackage)
		{
			return false;
		}
		loginClient->onKeyExchanged(exchangePackage->nonce_);
		return true;
	}

	bool ChallengePhase::handle(IPackage* package, Imp* loginClient)
	{
		auto challengeResponse = static_cast<ChallengeResponse*>(package);
		if(!challengeResponse)
		{
			return false;
		}
		if(challengeResponse->userName_.empty())
		{
			return false;
		}
		loginClient->challengeSignature_.swap(challengeResponse->nonce_);
		loginClient->authRequester_->request(challengeResponse->userName_, 
			loginClient->shared_from_this());

		return true;
	}
}


void LoginServer::onNewConnectionConnected(UPeer::Ptr upeer)
{
	auto imp = new login_server_detail::Imp();
	upeer->setAttachment(IIoAttachment::Ptr(imp));
	imp->setAuthRequester(authRequester_);
	imp->setLoginAuthDelegate(failedDelegate_, successDelegate_);
	imp->start(upeer);
}

void LoginServer::setAuthRequester(boost::shared_ptr<IAuthRequester> authRequester)
{
	authRequester_ = authRequester;
}

inline void LoginServer::setLoginFailedDelegate(LoginAuthDelegate loginFailed)
{
	failedDelegate_ = loginFailed;
}

inline void LoginServer::setLoginSuccessDelegate(LoginAuthDelegate loginSuccessed)
{
	successDelegate_ = loginSuccessed;
}

#pragma warning(pop)